//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.parameter;

public class ParameterExpandStub implements ParameterStub {

    final private static int    kMaximumRecursion = 25;

    private ParameterStub       stub;
    private ParameterProcessor  processor;
    private int                 recursion = 0;

    public ParameterExpandStub( ParameterStub stub, ParameterProcessor processor ) {
        this.stub = stub;
        this.processor = processor;
    }

    // Obtain a parameter, with the value expanded by the expandString method
    // as explained below.
    
    public String getParameter( String name ) {
        return expandString( stub.getParameter( name ));
    }

    // Expand a string according to the parameter definitions available 
    // from our ParameterProcessor.  If the string does not contain the
    // character '$', then nothing is done, and the string is returned
    // unmodified.  At each occurrence of the character '$', the following
    // characters in the string are parsed to produce a parameter name;
    // the parameter string with this name is obtained from the
    // ParameterProcesor and substituted into the string.
    //
    // Note that the usual usage has a ParameterProcessor obtaining
    // its parameters from a ParameterExpandStub which in turn uses the
    // original ParameterProcessor to obtain expanded parameter values,
    // allowing for recursive expansion.

    public String expandString( String string ) {
        String result = string;

        if( string != null ) {
            int nextParameter = string.indexOf( kParameterChar );
            if( nextParameter != -1 && recursion < kMaximumRecursion ) {
                recursion ++;

                StringBuffer buffer = new StringBuffer( );
                int afterParameter = 0;

                while( nextParameter != -1 ) {
                    buffer.append( string.substring( afterParameter, nextParameter ));
                    afterParameter = expandOneParameter( string, nextParameter + 1, buffer );
                    nextParameter = string.indexOf( kParameterChar, afterParameter );
                }

                buffer.append( string.substring( afterParameter, string.length( )));
                result = buffer.toString( );

                recursion --;
            }
        }

        return result;
    }

    // Expand one parameter out of string, with the parameter name starting at offset,
    // and append the resulting string to the buffer.  The index of the first character
    // of the source string not consumed by the parameter expansion is returned.
    //
    // If the character '$' appears at the end of the string or is not followed by a
    // letter, an underscore '_', another '$' character, or an opening brace '{', then
    // it is left in the source string unmodified.
    //
    // If the character '$' is followed by a letter or an underscore '_', then the
    // characters up to the first character which is not a letter, a *digit*, or an
    // underscore '_' are taken as a parameter name; the value is obtained from the
    // parameter processor passed into our constructor.
    //
    // If the character '$' is followed by another '$' character, then the pair of
    // '$' characters is replaced with a single '$' character.
    //
    // If the character '$' is followed by an opening brace '{', then the characters
    // up to the next closing brace '}' are taken as a parameter name; the value is
    // obtained from the parameter processor passed into our constructor.  Note that
    // in this case, the parameter name with length 0 is allowed, using "${}".  If the
    // closing brace is missing, then no expansion is done.

    final private static char   kParameterChar  = '$';
    final private static char   kBraceStartChar = '{';
    final private static char   kBraceEndChar   = '}';

    private int expandOneParameter( String string, int offset, StringBuffer buffer ) {
        int result;

        if( offset == string.length( )) {
            result = offset;
            buffer.append( kParameterChar );

        } else if( string.charAt( offset ) == kParameterChar ) {
            result = offset + 1;
            buffer.append( kParameterChar );

        } else if( string.charAt( offset ) == kBraceStartChar ) {
            int braceEndIndex = string.indexOf( kBraceEndChar, offset + 1 );

            if( braceEndIndex == -1 ) {
                result = offset;
                buffer.append( kParameterChar );
            } else {
                result = braceEndIndex + 1;
                buffer.append( processor.getString( string.substring( offset + 1, braceEndIndex ), "" ));
            }

        } else {
            result = indexOfParameterEnd( string, offset );
            if( result == offset )
                buffer.append( kParameterChar );
            else
                buffer.append( processor.getString( string.substring( offset, result ), "" ));
        }

        return result;
    }

    // Find the index of the first character which is not part of
    // the parameter name, starting at offset.  A parameter name
    // may contain letters, digits, and the underscore character,
    // and must not start with a digit.

    final private static String kParameterNameCharacters = "_";

    private int indexOfParameterEnd( String string, int offset ) {
        int result;

        for( result = offset; result < string.length( ); ++ result ) {
            char c = string.charAt( result );
            if( ! ( Character.isLowerCase( c ) ||
                    Character.isUpperCase( c ) ||
                    ( Character.isDigit( c ) && result != offset ) ||
                    kParameterNameCharacters.indexOf( c ) != -1 ))
                break;
        }
        
        return result;
    }
}
